"""Generate .docx version of the fiscal dominance paper with tables and charts."""

import sys
from pathlib import Path
import pandas as pd
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from docx import Document
from docx.shared import Inches, Pt, Cm, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT
from docx.oxml.ns import qn

PROJECT_DIR = Path(__file__).resolve().parent.parent
TABLES_DIR = PROJECT_DIR / "output" / "tables"
FIGURES_DIR = PROJECT_DIR / "output" / "figures"
FIGURES_DIR.mkdir(parents=True, exist_ok=True)

# ── Style helpers ──────────────────────────────────────────────────────────

def set_cell(cell, text, bold=False, align='left', size=9):
    cell.text = ''
    p = cell.paragraphs[0]
    run = p.add_run(str(text))
    run.font.size = Pt(size)
    run.font.name = 'Calibri'
    run.bold = bold
    if align == 'right':
        p.alignment = WD_ALIGN_PARAGRAPH.RIGHT
    elif align == 'center':
        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    # reduce cell padding
    tc = cell._tc
    tcPr = tc.get_or_add_tcPr()
    for side in ['top', 'bottom']:
        mar = tcPr.find(qn(f'w:tcMar'))
        # just set paragraph spacing instead
    p.paragraph_format.space_before = Pt(1)
    p.paragraph_format.space_after = Pt(1)

def shade_header(row, color='D9E2F3'):
    for cell in row.cells:
        shading = cell._tc.get_or_add_tcPr()
        s = shading.makeelement(qn('w:shd'), {
            qn('w:fill'): color,
            qn('w:val'): 'clear'
        })
        shading.append(s)

def add_table_from_data(doc, headers, rows, col_aligns=None, title=None, note=None):
    if title:
        p = doc.add_paragraph()
        run = p.add_run(title)
        run.bold = True
        run.font.size = Pt(10)
        run.font.name = 'Calibri'
        p.paragraph_format.space_before = Pt(6)
        p.paragraph_format.space_after = Pt(3)

    table = doc.add_table(rows=1 + len(rows), cols=len(headers))
    table.style = 'Table Grid'
    table.alignment = WD_TABLE_ALIGNMENT.CENTER

    # header
    for i, h in enumerate(headers):
        align = 'center' if i == 0 else 'center'
        set_cell(table.rows[0].cells[i], h, bold=True, align=align, size=9)
    shade_header(table.rows[0])

    # data rows
    if col_aligns is None:
        col_aligns = ['left'] + ['right'] * (len(headers) - 1)
    for r, row_data in enumerate(rows):
        for c, val in enumerate(row_data):
            set_cell(table.rows[r+1].cells[c], val, align=col_aligns[c], size=9)

    if note:
        p = doc.add_paragraph()
        run = p.add_run(note)
        run.font.size = Pt(8)
        run.font.italic = True
        run.font.name = 'Calibri'
        p.paragraph_format.space_before = Pt(1)
        p.paragraph_format.space_after = Pt(6)

    return table

def add_heading(doc, text, level=1):
    h = doc.add_heading(text, level=level)
    for run in h.runs:
        run.font.color.rgb = RGBColor(0, 0, 0)
        run.font.name = 'Calibri'

def add_para(doc, text, size=11, bold=False, italic=False, space_after=6):
    p = doc.add_paragraph()
    run = p.add_run(text)
    run.font.size = Pt(size)
    run.font.name = 'Calibri'
    run.bold = bold
    run.italic = italic
    p.paragraph_format.space_after = Pt(space_after)
    return p

def fmt(val, decimals=3):
    if isinstance(val, str):
        return val
    if pd.isna(val):
        return ''
    return f'{val:.{decimals}f}'

def fmt_p(p):
    if pd.isna(p):
        return ''
    if p < 0.001:
        return '< 0.001'
    return f'{p:.3f}'

def stars(p):
    if pd.isna(p):
        return ''
    if p < 0.01:
        return '***'
    if p < 0.05:
        return '**'
    if p < 0.1:
        return '*'
    return ''

# ── Load data ──────────────────────────────────────────────────────────────

bohn = pd.read_csv(TABLES_DIR / "phase2_bohn_results.csv")
rolling = pd.read_csv(TABLES_DIR / "phase2_rolling_bohn.csv")
decomp = pd.read_csv(TABLES_DIR / "phase2b_decomposition_results.csv")
rg = pd.read_csv(TABLES_DIR / "phase3_rg_results.csv")
rg_het = pd.read_csv(TABLES_DIR / "phase3b_rg_heterogeneity.csv")
regime = pd.read_csv(TABLES_DIR / "phase4_regime_results.csv")
robust = pd.read_csv(TABLES_DIR / "phase6_robustness_results.csv")
z1_comp = pd.read_csv(TABLES_DIR / "phase6_z1_comparison.csv")
trajectories = pd.read_csv(TABLES_DIR / "phase2b_debt_trajectories_v2.csv")
threshold = pd.read_csv(TABLES_DIR / "phase2b_threshold_crossing.csv")
summary = pd.read_csv(TABLES_DIR / "phase1_summary_stats.csv")

# ── Generate figures ───────────────────────────────────────────────────────

plt.rcParams.update({
    'font.family': 'sans-serif',
    'font.size': 10,
    'axes.spines.top': False,
    'axes.spines.right': False,
})

# Figure 1: Rolling Bohn coefficient
fig, ax = plt.subplots(figsize=(8, 4.5))
mid = (rolling['window_start'] + rolling['window_end']) / 2
ax.plot(mid, rolling['bohn_beta'], 'o-', color='#2166AC', linewidth=2, markersize=5)
ax.fill_between(mid,
                rolling['bohn_beta'] - 1.96 * rolling['bohn_se'],
                rolling['bohn_beta'] + 1.96 * rolling['bohn_se'],
                alpha=0.2, color='#2166AC')
ax.axhline(0, color='black', linewidth=0.8, linestyle='--')
ax.set_xlabel('Window Midpoint')
ax.set_ylabel('Bohn Coefficient (β₁)')
ax.set_title('Rolling 15-Year Bohn Coefficient, 1990–2024')
sig = rolling['bohn_p'] < 0.05
ax.scatter(mid[sig], rolling['bohn_beta'][sig], color='#2166AC', s=50, zorder=5)
ax.scatter(mid[~sig], rolling['bohn_beta'][~sig], color='white', edgecolors='#2166AC',
           s=50, zorder=5, linewidths=1.5)
ax.annotate('Filled = p < 0.05', xy=(0.02, 0.95), xycoords='axes fraction',
            fontsize=8, va='top')
fig.tight_layout()
fig.savefig(FIGURES_DIR / "fig1_rolling_bohn.png", dpi=200)
plt.close()
print("  Figure 1: Rolling Bohn")

# Figure 2: Expenditure vs Revenue sensitivity (bar chart)
fig, ax = plt.subplots(figsize=(6, 4))
labels = ['Expenditure', 'Revenue', 'Fiscal Gap']
vals = [119.7, 47.6, 45.5]
ses = [20.2, 21.3, 10.9]
colors = ['#D6604D', '#4393C3', '#878787']
bars = ax.bar(labels, vals, yerr=[1.96*s for s in ses], color=colors, capsize=5,
              edgecolor='white', linewidth=1.5)
ax.set_ylabel('OADR Coefficient (pp of GDP per unit OADR)')
ax.set_title('Expenditure-Revenue Asymmetry: Response to Aging')
ax.axhline(0, color='black', linewidth=0.5)
for bar, val in zip(bars, vals):
    ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 5,
            f'+{val:.0f}', ha='center', va='bottom', fontsize=10, fontweight='bold')
fig.tight_layout()
fig.savefig(FIGURES_DIR / "fig2_expenditure_asymmetry.png", dpi=200)
plt.close()
print("  Figure 2: Expenditure asymmetry")

# Figure 3: OADR spline on r-g — two panels
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.5))

# Panel A: Scatter + fitted piecewise-linear spline
fiscal_panel = pd.read_csv(PROJECT_DIR / "data" / "processed" / "fiscal_panel.csv")
plot_df = fiscal_panel.dropna(subset=['old_dep', 'r_minus_g']).copy()
ax1.scatter(plot_df['old_dep'] * 100, plot_df['r_minus_g'], alpha=0.08, s=8, color='#BDBDBD')
# Fitted spline with knot at 20%
knot = 20
base_slope = 28.8  # from regression
spline_slope = -80.5
intercept_adj = 3.0  # approximate constant effect
oadr_range = np.linspace(0, 50, 200)
fitted = intercept_adj + base_slope * (oadr_range / 100) + spline_slope * np.maximum(0, oadr_range / 100 - knot / 100)
ax1.plot(oadr_range, fitted, color='#D6604D', linewidth=2.5, label='Fitted spline (knot=20%)')
ax1.axvline(knot, color='gray', linewidth=0.8, linestyle='--', alpha=0.6)
ax1.axhline(0, color='black', linewidth=0.5)
ax1.set_xlabel('Old-Age Dependency Ratio (%)')
ax1.set_ylabel('Nominal r − g (pp)')
ax1.set_title('A. OADR Spline on Nominal r−g')
ax1.set_xlim(0, 50)
ax1.set_ylim(-30, 60)
ax1.legend(fontsize=8)
ax1.annotate(f'Knot = {knot}%', xy=(knot, -25), fontsize=9, ha='center', color='gray')

# Panel B: Marginal effect by OADR level
oadr_eval = np.linspace(5, 45, 100)
marginal = np.where(oadr_eval < knot, base_slope, base_slope + spline_slope)
ax2.fill_between(oadr_eval, marginal, 0, where=marginal > 0, alpha=0.3, color='#D6604D', label='Aging raises r−g')
ax2.fill_between(oadr_eval, marginal, 0, where=marginal < 0, alpha=0.3, color='#4393C3', label='Aging lowers r−g')
ax2.plot(oadr_eval, marginal, color='black', linewidth=2)
ax2.axhline(0, color='black', linewidth=0.8, linestyle='--')
ax2.axvline(knot, color='gray', linewidth=0.8, linestyle='--', alpha=0.6)
ax2.set_xlabel('Old-Age Dependency Ratio (%)')
ax2.set_ylabel('Marginal Effect on r−g')
ax2.set_title('B. Marginal Effect of OADR on r−g')
ax2.legend(fontsize=8, loc='lower left')
ax2.annotate(f'+{base_slope:.1f}', xy=(10, base_slope + 2), fontsize=10, fontweight='bold', color='#D6604D')
ax2.annotate(f'{base_slope + spline_slope:.1f}', xy=(35, base_slope + spline_slope - 4), fontsize=10, fontweight='bold', color='#4393C3')

fig.tight_layout()
fig.savefig(FIGURES_DIR / "fig3_rg_spline.png", dpi=200, bbox_inches='tight')
plt.close()
print("  Figure 3: r-g OADR spline")

# Figure 4b: Safe vs non-safe coefficient comparison
fig, ax = plt.subplots(figsize=(6, 4))
labels = ['Safe Issuers\n(AA− or above)', 'Non-Safe\nIssuers']
coefs = [-26.5, 7.1]
pvals = [0.0000, 0.4474]
colors_safe = ['#4393C3', '#BDBDBD']
bars = ax.bar(labels, coefs, color=colors_safe, edgecolor='white', linewidth=1.5, width=0.5)
ax.axhline(0, color='black', linewidth=0.8)
ax.set_ylabel('OADR Coefficient on Nominal r−g')
ax.set_title('Safe vs. Non-Safe Issuers: OADR Effect on r−g')
for bar, p, c in zip(bars, pvals, coefs):
    y = bar.get_height()
    label = f'{c:+.1f}\np={p:.4f}' if p < 0.001 else f'{c:+.1f}\np={p:.3f}'
    ax.text(bar.get_x() + bar.get_width()/2,
            y - 2 if y < 0 else y + 1,
            label, ha='center', va='top' if y < 0 else 'bottom', fontsize=9, fontweight='bold')
ax.annotate('Blue = p < 0.05; Gray = not significant', xy=(0.02, 0.02),
            xycoords='axes fraction', fontsize=8)
fig.tight_layout()
fig.savefig(FIGURES_DIR / "fig3b_safe_split.png", dpi=200)
plt.close()
print("  Figure 3b: Safe vs non-safe")

# Figure 5: Debt trajectories — selected countries
fig, ax = plt.subplots(figsize=(9, 5.5))
traj = trajectories.copy()
traj = traj.set_index('iso3')
years = [2024, 2030, 2035, 2040, 2050, 2060]
# Select key countries
selected = ['USA', 'GBR', 'JPN', 'CHN', 'BRA', 'DEU', 'IND', 'FRA', 'ITA', 'ZAF']
colors_map = {
    'USA': '#2166AC', 'GBR': '#4393C3', 'JPN': '#762A83', 'CHN': '#D6604D',
    'BRA': '#1B7837', 'DEU': '#F4A582', 'IND': '#B2ABD2', 'FRA': '#92C5DE',
    'ITA': '#E7D4E8', 'ZAF': '#A6D96A'
}
styles = {
    'USA': '-', 'GBR': '-', 'JPN': '--', 'CHN': '-',
    'BRA': '-', 'DEU': '--', 'IND': '-.', 'FRA': '-.',
    'ITA': '--', 'ZAF': '-'
}
for iso in selected:
    if iso in traj.index:
        vals = [traj.loc[iso, str(y)] for y in years]
        # Cap at 500 for readability
        vals_plot = [min(v, 500) for v in vals]
        ax.plot(years, vals_plot, styles.get(iso, '-'), color=colors_map.get(iso, 'gray'),
                linewidth=2, label=iso, marker='o', markersize=4)
ax.axhline(100, color='gray', linewidth=0.5, linestyle=':')
ax.axhline(200, color='gray', linewidth=0.5, linestyle=':')
ax.set_xlabel('Year')
ax.set_ylabel('Debt-to-GDP (%)')
ax.set_title('Projected Debt Trajectories, 2024–2060')
ax.set_ylim(-50, 520)
ax.legend(bbox_to_anchor=(1.01, 1), loc='upper left', fontsize=9, frameon=False)
ax.text(2061, 100, '100%', fontsize=8, va='center', color='gray')
ax.text(2061, 200, '200%', fontsize=8, va='center', color='gray')
fig.tight_layout()
fig.savefig(FIGURES_DIR / "fig4_debt_trajectories.png", dpi=200, bbox_inches='tight')
plt.close()
print("  Figure 4: Debt trajectories")

# Figure 6: Threshold crossing heatmap
fig, ax = plt.subplots(figsize=(8, 5))
tc = threshold.copy()
tc = tc.sort_values('Debt 2024', ascending=False)
countries = tc['Country'].tolist()
thresholds_cols = ['100%', '150%', '200%', '300%']
# Create numeric matrix for coloring
n_countries = len(countries)
n_thresh = len(thresholds_cols)
matrix = np.full((n_countries, n_thresh), np.nan)
for i, (_, row) in enumerate(tc.iterrows()):
    for j, col in enumerate(thresholds_cols):
        val = str(row[col]).strip()
        if val == 'already':
            matrix[i, j] = 2024
        elif val.startswith('>'):
            matrix[i, j] = 2065
        else:
            try:
                matrix[i, j] = int(val)
            except:
                matrix[i, j] = 2065

# Color by urgency
from matplotlib.colors import LinearSegmentedColormap
cmap = LinearSegmentedColormap.from_list('urgency',
    ['#D73027', '#FC8D59', '#FEE08B', '#D9EF8B', '#91CF60', '#1A9850'])
im = ax.imshow(matrix, cmap=cmap, aspect='auto', vmin=2024, vmax=2065)

ax.set_xticks(range(n_thresh))
ax.set_xticklabels(thresholds_cols)
ax.set_yticks(range(n_countries))
ax.set_yticklabels(countries, fontsize=9)
ax.set_title('Year of Debt Threshold Crossing')
ax.set_xlabel('Debt-to-GDP Threshold')

# Annotate cells
for i, (_, row) in enumerate(tc.iterrows()):
    for j, col in enumerate(thresholds_cols):
        val = str(row[col]).strip()
        color = 'white' if matrix[i,j] < 2035 else 'black'
        ax.text(j, i, val, ha='center', va='center', fontsize=8, color=color,
                fontweight='bold' if val == 'already' else 'normal')

plt.colorbar(im, ax=ax, label='Year', shrink=0.8)
fig.tight_layout()
fig.savefig(FIGURES_DIR / "fig5_threshold_heatmap.png", dpi=200, bbox_inches='tight')
plt.close()
print("  Figure 5: Threshold heatmap")

# ── Build document ─────────────────────────────────────────────────────────

print("\nBuilding .docx...")
doc = Document()

# Set default font
style = doc.styles['Normal']
font = style.font
font.name = 'Calibri'
font.size = Pt(11)
style.paragraph_format.space_after = Pt(6)
style.paragraph_format.line_spacing = 1.15

# Title page
p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = p.add_run('\n\n\n')
p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = p.add_run('Population Aging and the Fiscal Sustainability Trap')
run.font.size = Pt(22)
run.font.name = 'Calibri'
run.bold = True
p.paragraph_format.space_after = Pt(12)

p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = p.add_run('Expenditure Asymmetry, Debt Dynamics, and the Heterogeneous\nInterest Rate Channel')
run.font.size = Pt(14)
run.font.name = 'Calibri'
run.font.color.rgb = RGBColor(100, 100, 100)
p.paragraph_format.space_after = Pt(24)

p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = p.add_run('Brian Peters')
run.font.size = Pt(14)
run.font.name = 'Calibri'
p.paragraph_format.space_after = Pt(6)

p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = p.add_run('March 2026')
run.font.size = Pt(12)
run.font.name = 'Calibri'

doc.add_page_break()

# Abstract
add_heading(doc, 'Abstract', level=1)
abstract = (
    "Does population aging undermine fiscal sustainability? We test this using the Bohn (1998) "
    "fiscal reaction function framework across 181 countries from 1990 to 2024, incorporating "
    "Fair-Dominguez demographic polynomials. The baseline Bohn coefficient is positive but small "
    "(0.005, p = 0.043), confirming weak fiscal discipline in the pooled sample. When estimated "
    "on the cyclically-adjusted structural balance, the coefficient reverses sign (−0.011, p = 0.005): "
    "governments actively loosen fiscal policy as debt rises. Aging operates through a stark "
    "expenditure-revenue asymmetry: a 10 percentage point increase in the old-age dependency "
    "ratio raises government expenditure by 12 percentage points of GDP but revenue by only 5 "
    "points, opening a fiscal gap of approximately 5 points per unit OADR. This gap is 80% "
    "non-health (pensions, social transfers). The pooled r−g null (p = 0.44) is a composition "
    "artifact: a piecewise-linear OADR spline reveals that below 20% OADR, aging raises r−g "
    "(+28.8, p = 0.007); above the threshold, aging compresses r−g (spline = −80.5, p = 0.0005). "
    "Among safe issuers (AA− or above), OADR lowers nominal r−g by 26.5 per unit (p < 0.0001), "
    "while the effect is null for non-safe issuers. The real r−g channel is significant pre-GFC but "
    "extinguished by QE post-GFC. The expenditure-revenue asymmetry is the universal fiscal "
    "threat from aging; the interest rate channel provides additional relief only for aged safe-issuer "
    "economies."
)
add_para(doc, abstract, size=10, italic=True)

p = doc.add_paragraph()
run = p.add_run('Keywords: ')
run.bold = True
run.font.size = Pt(10)
run = p.add_run('fiscal sustainability, demographics, aging, Bohn test, fiscal dominance, debt dynamics, pension spending')
run.font.size = Pt(10)

p = doc.add_paragraph()
run = p.add_run('JEL Codes: ')
run.bold = True
run.font.size = Pt(10)
run = p.add_run('H63, H55, J11, E62')
run.font.size = Pt(10)

doc.add_page_break()

# ── 1. Introduction ───────────────────────────────────────────────────────

add_heading(doc, '1. Introduction', level=1)

add_para(doc, (
    "Government debt ratios across the advanced world have reached peacetime records. The United "
    "States surpassed 120% of GDP in 2024, France exceeded 117%, and Japan remains above 224%. "
    "These debt levels coincide with an unprecedented demographic transition: the populations of "
    "these same countries are aging rapidly, with old-age dependency ratios projected to double in "
    "many economies by 2060. The question is whether these two trends are connected—whether "
    "aging systematically undermines the fiscal discipline that keeps public debt sustainable."
))

add_para(doc, (
    "The theoretical case for demographic fiscal stress operates through multiple channels. First, "
    "aging directly increases age-related public spending—pensions, healthcare, long-term care—"
    "creating persistent primary deficits (Lee and Mason 2003; Yared 2019). Second, aging may "
    "depress the natural rate of interest through lifecycle savings effects (Carvalho et al. 2016; "
    "Rachel and Smith 2017), but simultaneously reduce potential growth, with the net effect on the "
    "interest-growth differential (r−g) determining debt dynamics (Blanchard 2019). Third, as debt "
    "accumulates, the fiscal reaction function itself may weaken: governments facing aging "
    "electorates may find it politically impossible to tighten fiscal policy in response to rising "
    "debt, creating what Sargent and Wallace (1981) termed 'fiscal dominance.'"
))

add_para(doc, (
    "Despite this rich theoretical structure, there is no systematic cross-country empirical test of "
    "whether demographics predict the breakdown of fiscal sustainability. This paper fills that gap. "
    "We estimate the Bohn fiscal reaction function for 181 countries over 1990–2024, using "
    "Fair-Dominguez demographic polynomials to capture the full age distribution."
))

add_para(doc, 'Our results yield a clear and surprising conclusion:', bold=True)

findings = [
    ("The Bohn coefficient is positive but fragile.", "The pooled primary-balance reaction to lagged debt is +0.005 (p = 0.043). But on the structural balance, it is −0.011 (p = 0.005): governments actively loosen discretionary fiscal policy as debt rises."),
    ("Demographics do not weaken the Bohn coefficient.", "Interaction terms between lagged debt and demographic polynomials are insignificant (all p > 0.41). Aging does not erode the fiscal reaction function."),
    ("The expenditure-revenue asymmetry is the core mechanism.", "A 10 pp increase in the OADR raises expenditure by 12 pp of GDP but revenue by only 5 pp. This 2.5:1 asymmetry is 80% pensions, not healthcare."),
    ("The r−g effect is heterogeneous, not null.", "The pooled r−g insignificance masks sharp heterogeneity: an OADR spline at 20% shows aging raises r−g below the threshold (+28.8, p = 0.007) but compresses it above (−80.5, p = 0.0005). Among safe issuers, OADR lowers r−g by 26.5 (p < 0.0001); the effect is null for non-safe issuers."),
    ("Demographics drive debt accumulation directly.", "Z₁ coefficient on annual debt change: +22.6 (p = 0.010)."),
    ("Six major economies are on explosive debt trajectories.", "The US, UK, France, China, India, and Brazil will all exceed 150% debt-to-GDP by 2040 absent policy adjustment."),
]
for i, (title, detail) in enumerate(findings, 1):
    p = doc.add_paragraph()
    run = p.add_run(f'{i}. {title} ')
    run.bold = True
    run.font.size = Pt(11)
    run = p.add_run(detail)
    run.font.size = Pt(11)
    p.paragraph_format.space_after = Pt(4)
    p.paragraph_format.left_indent = Cm(0.5)

# ── 2. Literature Review ──────────────────────────────────────────────────

doc.add_page_break()
add_heading(doc, '2. Literature Review', level=1)

add_heading(doc, '2.1 Fiscal Sustainability and the Bohn Test', level=2)
add_para(doc, (
    "Bohn (1998) established the standard test: if the primary balance responds positively to lagged "
    "debt (β > 0), the government's intertemporal budget constraint is satisfied. Ghosh et al. (2013) "
    "extended this to show 'fiscal fatigue'—the response weakening at high debt levels—but did not "
    "incorporate demographics."
))

add_heading(doc, '2.2 Demographics, Interest Rates, and Growth', level=2)
add_para(doc, (
    "Carvalho et al. (2016) develop an OLG model in which aging depresses the equilibrium real "
    "interest rate. Rachel and Smith (2017) estimate that demographics account for roughly 90 basis "
    "points of the secular decline in global real rates. The net effect on r−g is theoretically "
    "ambiguous: aging depresses both r (lower savings demand) and g (smaller labor force). "
    "Blanchard (2019) argues that r < g makes debt less costly, but this may not persist. "
    "Piketty (2014) placed r > g at the center of wealth inequality dynamics; the fiscal "
    "sustainability implication is symmetric: when r > g, public debt compounds faster than GDP. "
    "Caballero, Farhi, and Gourinchas (2008, 2017) argue that global safe asset demand concentrates "
    "among highly-rated sovereigns, creating a wedge: aging may compress r−g for safe issuers "
    "while leaving it unchanged for non-safe issuers. The theoretical ambiguity is thus resolved "
    "not by aggregate theory but by recognizing threshold dependence and sovereign credit quality."
))

add_heading(doc, '2.3 Demographics and Fiscal Dominance', level=2)
add_para(doc, (
    "Sargent and Wallace (1981) described 'unpleasant monetarist arithmetic': when fiscal policy "
    "does not adjust, monetary policy must monetize. Katagiri et al. (2020) connect demographics to "
    "fiscal dominance theoretically. Plantin (2024) analyzes fiscal dominance regimes. No paper has "
    "tested the demographic-fiscal dominance nexus empirically across a broad country panel."
))

# ── 3. Data ────────────────────────────────────────────────────────────────

doc.add_page_break()
add_heading(doc, '3. Data', level=1)

add_para(doc, (
    "We construct a panel of 181 countries over 1990–2024 by merging fiscal variables from the "
    "IMF World Economic Outlook (April 2025), demographic and macroeconomic variables from our "
    "base panel (Fair-Dominguez polynomials, OADR, GDP, Chinn-Ito KAOPEN), and interest rates "
    "following a hierarchy: 10-year government bond yields > policy rate > lending rate."
))

add_para(doc, (
    "The output gap is computed using the Hodrick-Prescott filter on log GDP per capita (λ = 6.25 "
    "for annual data), replacing the IMF's own measure available for only 27 countries. All fiscal "
    "variables are winsorized at the 1st and 99th percentiles."
))

# Summary statistics table
add_table_from_data(doc,
    headers=['Variable', 'Mean', 'Std. Dev.', 'Min', 'Max', 'N'],
    rows=[
        ['Govt Debt/GDP (%)', '55.2', '39.3', '0.0', '228.7', '5,568'],
        ['Primary Balance/GDP (%)', '−0.5', '5.1', '−18.4', '19.7', '5,699'],
        ['Structural Balance/GDP (%)', '−2.6', '3.4', '−12.6', '5.6', '2,361'],
        ['r−g nominal (pp)', '8.9', '13.4', '−8.2', '79.4', '4,276'],
        ['r−g real (pp)', '0.4', '11.8', '−66.2', '38.4', '4,241'],
        ['Old-Age Dependency Ratio', '0.125', '0.092', '0.010', '1.099', '8,295'],
        ['Z₁', '−1.27', '1.37', '−3.98', '3.54', '8,295'],
        ['Real GDP Growth (%)', '3.4', '6.3', '−54.3', '148.0', '6,461'],
        ['KAOPEN', '0.20', '1.56', '−1.94', '2.28', '5,807'],
    ],
    title='Table 1: Summary Statistics',
    note='Source: IMF WEO, UN WPP, Penn World Tables, Chinn-Ito database. 1990–2024.'
)

# ── 4. Bohn Test ──────────────────────────────────────────────────────────

doc.add_page_break()
add_heading(doc, '4. The Bohn Fiscal Reaction Function', level=1)

add_heading(doc, '4.1 Baseline Results', level=2)
add_para(doc, (
    "We estimate the Bohn fiscal reaction function using pooled GLS with AR(1) correction: "
    "pb = β₁·debt_lag + β₂·output_gap + β₃·govt_exp_gap + constant. The key parameter is β₁: "
    "a positive value indicates fiscal sustainability."
))

# Extract Bohn results
def get_bohn_row(model_prefix, label):
    mask = bohn['model'].str.startswith(model_prefix)
    sub = bohn[mask]
    debt_row = sub[sub['variable'] == 'debt_lag']
    if len(debt_row) == 0:
        return None
    r = debt_row.iloc[0]
    return [label, fmt(r['coefficient'], 4), fmt(r['std_error'], 4),
            fmt_p(r['p_value']), f"{int(r['n_obs']):,}", str(int(r['n_countries'])),
            fmt(r['r_squared'], 3)]

bohn_rows = []
for prefix, label in [
    ('Model 1', '(1) Baseline Bohn'),
    ('Model 2', '(2) + Z levels'),
    ('Model 3', '(3) + debt × Z interactions'),
    ('Model 4', '(4) + debt × OADR'),
    ('Model 5', '(5) + debt × Z × KAOPEN'),
    ('Model 6', '(6) Structural Bohn'),
]:
    row = get_bohn_row(prefix, label)
    if row:
        bohn_rows.append(row)

add_table_from_data(doc,
    headers=['Model', 'β₁ (debt_lag)', 'SE', 'p-value', 'N', 'Countries', 'R²'],
    rows=bohn_rows,
    title='Table 2: Bohn Fiscal Reaction Function',
    note='Pooled GLS with AR(1). Dependent variable: primary balance/GDP (Models 1–5) or structural balance/GDP (Model 6). '
         'Controls include HP-filtered output gap and government expenditure gap.'
)

add_para(doc, (
    "The baseline Bohn coefficient (Model 1) is positive and significant at the 5% level: a 10 "
    "percentage point increase in the debt ratio is associated with a 0.054 point improvement in "
    "the primary balance. This is a weak response but statistically significant."
))

add_heading(doc, '4.2 Demographics Do Not Weaken the Bohn Coefficient', level=2)
add_para(doc, (
    "Model 3 interacts lagged debt with the demographic polynomials. If aging weakens fiscal "
    "discipline, these interactions should be negative and significant. They are not: all three "
    "interaction terms have p-values above 0.41. Model 4 (debt × OADR, p = 0.737) and Model 5 "
    "(triple interaction with KAOPEN, all p > 0.25) confirm this null."
))

add_heading(doc, '4.3 The Structural Balance Reveals Active Loosening', level=2)
add_para(doc, (
    "Model 6 replaces the primary balance with the structural balance. The Bohn coefficient "
    "reverses to −0.011 (p = 0.005): governments with higher debt run looser discretionary fiscal "
    "policy. On the identical 82-country subsample (Table A1), the primary Bohn coefficient is "
    "0.010 (p = 0.001) while the structural coefficient is −0.006 (p = 0.061). "
    "The sign reversal is robust to alternative gap measures (Table A2): HP filter (−0.005), "
    "IMF WEO gap (−0.009), Hamilton filter (−0.003), and growth+inflation controls (−0.003) all "
    "produce negative structural Bohn coefficients, though statistical significance is sensitive "
    "to gap measurement and sample."
))

add_heading(doc, '4.4 Rolling-Window Evidence', level=2)
add_para(doc, (
    "The Bohn coefficient displays a clear arc: negative in the 1990s, positive during 2001–2018, "
    "collapsed to insignificance since windows including 2020."
))

# Insert rolling Bohn figure
doc.add_picture(str(FIGURES_DIR / "fig1_rolling_bohn.png"), width=Inches(5.5))
last_paragraph = doc.paragraphs[-1]
last_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_para(doc, 'Figure 1: Rolling 15-year Bohn coefficient with 95% confidence interval. '
         'Filled markers indicate p < 0.05.', size=9, italic=True)

# Rolling table
rolling_rows = []
for _, r in rolling.iterrows():
    rolling_rows.append([
        f"{int(r['window_start'])}–{int(r['window_end'])}",
        fmt(r['bohn_beta'], 4),
        fmt_p(r['bohn_p']),
        str(int(r['n_countries']))
    ])

add_table_from_data(doc,
    headers=['Window', 'β₁', 'p-value', 'Countries'],
    rows=rolling_rows,
    title='Table 3: Rolling Bohn Coefficient',
    note='15-year rolling windows. Dependent variable: primary balance/GDP.'
)

# ── 5. Expenditure Decomposition ─────────────────────────────────────────

doc.add_page_break()
add_heading(doc, '5. The Expenditure-Revenue Asymmetry', level=1)

add_heading(doc, '5.1 Core Decomposition', level=2)
add_para(doc, (
    "If demographics do not weaken the Bohn coefficient, how does aging threaten fiscal sustainability? "
    "We decompose the fiscal gap by estimating separate regressions of expenditure/GDP and "
    "revenue/GDP on demographics."
))

# Insert expenditure asymmetry figure
doc.add_picture(str(FIGURES_DIR / "fig2_expenditure_asymmetry.png"), width=Inches(4.5))
last_paragraph = doc.paragraphs[-1]
last_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_para(doc, 'Figure 2: OADR coefficient on expenditure, revenue, and the fiscal gap. '
         'Error bars show 95% confidence intervals.', size=9, italic=True)

# Decomposition table
decomp_rows = [
    ['Govt Expenditure/GDP', '+119.7', '20.2', '< 0.001', '0.417', '3,149'],
    ['Govt Revenue/GDP', '+47.6', '21.3', '0.026', '0.497', '3,160'],
    ['Fiscal Gap (Exp.−Rev.)', '+45.5', '10.9', '< 0.001', '0.067', '3,532'],
]
add_table_from_data(doc,
    headers=['Dependent Variable', 'OADR Coeff.', 'SE', 'p-value', 'R²', 'N'],
    rows=decomp_rows,
    title='Table 4: Expenditure and Revenue Decomposition',
    note='OADR coefficient with quadratic term. Controls: debt_lag, KAOPEN, log_rel_opw, nfa_gdp_lag.'
)

add_para(doc, (
    "The expenditure sensitivity is 2.5 times the revenue sensitivity: a 10 pp increase in the OADR "
    "raises expenditure by ~12 pp of GDP but revenue by only ~5 pp, opening a structural fiscal gap."
))

add_heading(doc, '5.2 Health vs. Non-Health Spending', level=2)

health_rows = [
    ['Health Expenditure/GDP', '+22.8', '3.6', '< 0.001', '19%'],
    ['Non-Health Expenditure/GDP', '+94.1', '19.4', '< 0.001', '79%'],
    ['Total Expenditure/GDP', '+119.1', '21.3', '< 0.001', '100%'],
]
add_table_from_data(doc,
    headers=['Component', 'OADR Coeff.', 'SE', 'p-value', 'Share'],
    rows=health_rows,
    title='Table 5: Health vs. Non-Health Expenditure Decomposition',
    note='135 countries with health expenditure data. OADR with quadratic term.'
)

add_para(doc, (
    "Non-health expenditure—predominantly pensions and social transfers—accounts for ~80% of "
    "the demographic spending pressure. Healthcare reform alone addresses only one-fifth of the "
    "fiscal burden. Pension reform is the critical lever."
))

# ── 6. r-g Dynamics ──────────────────────────────────────────────────────

doc.add_page_break()
add_heading(doc, '6. The Interest Rate Channel', level=1)

add_heading(doc, '6.1 The Pooled Null', level=2)
add_para(doc, (
    "A large theoretical literature predicts that aging depresses the natural rate of interest. "
    "If this outpaces the reduction in potential growth, r−g falls and debt dynamics improve; if "
    "growth falls faster, r−g rises. The pooled result appears to show that the effects cancel:"
))

rg_rows = [
    ['Interest Rate', '−55.6', '0.001', '3,161', '135', '0.147'],
    ['Real GDP Growth', '−7.0', '0.209', '4,239', '164', '0.047'],
    ['r−g (nominal)', '−14.8', '0.441', '3,044', '129', '0.106'],
    ['r−g (real)', '+33.5', '0.039', '3,012', '129', '0.047'],
]
add_table_from_data(doc,
    headers=['Dependent Variable', 'Z₁ Coeff.', 'p-value', 'N', 'Countries', 'R²'],
    rows=rg_rows,
    title='Table 6: Demographics and r−g Dynamics (Pooled)',
    note='Controls: fiscal_bal_gdp, KAOPEN, log_rel_opw, nfa_gdp_lag. Mixed rate hierarchy.'
)

add_para(doc, (
    "This pooled null is misleading. The nominal r−g insignificance (p = 0.441) is a composition "
    "artifact masking sharp heterogeneity along three dimensions: OADR threshold effects, "
    "sovereign credit quality, and the pre/post-GFC structural break."
), bold=True)

add_heading(doc, '6.2 The OADR Threshold', level=2)

doc.add_picture(str(FIGURES_DIR / "fig3_rg_spline.png"), width=Inches(5.8))
last_paragraph = doc.paragraphs[-1]
last_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_para(doc, 'Figure 3: OADR spline on nominal r\u2212g. Panel A: scatter with fitted piecewise-linear '
         'spline (knot at 20%). Panel B: marginal effect showing sign reversal at threshold.',
         size=9, italic=True)

add_table_from_data(doc,
    headers=['Knot', 'OADR (base slope)', 'Spline (above knot)', 'N', 'Countries', 'R²'],
    rows=[
        ['15%', '+36.6 (p=0.020)**', '−61.5 (p=0.012)**', '3,044', '129', '0.112'],
        ['20%', '+28.8 (p=0.007)***', '−80.5 (p=0.0005)***', '3,044', '129', '0.116'],
        ['25%', '+11.2 (p=0.183)', '−62.2 (p=0.031)**', '3,044', '129', '0.117'],
        ['30%', '+5.0 (p=0.504)', '−61.3 (p=0.165)', '3,044', '129', '0.116'],
    ],
    title='Table 6b: OADR Spline on Nominal r−g',
    note='Piecewise-linear spline: r−g = α + β₁·OADR + β₂·max(0, OADR−knot) + γX + u. '
         'PanelGLS with AR(1).'
)

add_para(doc, (
    "Below 20% OADR, aging raises nominal r−g by 28.8 per unit (p = 0.007): growth falls faster "
    "than rates in young economies. Above 20%, the total slope is 28.8 − 80.5 = −51.7: deep "
    "lifecycle savings compress rates faster than growth falls. This connects to Piketty (2014): "
    "below the threshold, aging worsens r > g; above it, aging ameliorates the condition."
))

add_heading(doc, '6.3 Safe vs. Non-Safe Issuers', level=2)

doc.add_picture(str(FIGURES_DIR / "fig3b_safe_split.png"), width=Inches(4.5))
last_paragraph = doc.paragraphs[-1]
last_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_para(doc, 'Figure 3b: OADR coefficient on nominal r\u2212g by sovereign credit quality. Safe issuers '
         '(AA\u2212 or above) show strong negative effect; non-safe issuers show null.',
         size=9, italic=True)

add_table_from_data(doc,
    headers=['Sample', 'Specification', 'Coefficient', 'p-value', 'N', 'Countries'],
    rows=[
        ['Safe issuers (AA−+)', 'OADR → nom. r−g', '−26.5', '<0.0001', '634', '25'],
        ['Safe issuers', 'Z₁ → nom. r−g', '+18.7', '0.283', '634', '25'],
        ['Safe issuers', 'OADR → real r−g', '−5.9', '0.247', '610', '25'],
        ['Non-safe issuers', 'OADR → nom. r−g', '+7.1', '0.447', '2,410', '111'],
        ['Non-safe issuers', 'Z₁ → nom. r−g', '−19.3', '0.416', '2,410', '111'],
        ['Non-safe issuers', 'OADR → real r−g', '−0.3', '0.974', '2,402', '111'],
    ],
    title='Table 6c: Safe vs. Non-Safe Issuer Split on r−g',
    note='Safe issuer = S&P rating AA− or above. PanelGLS with AR(1).'
)

add_para(doc, (
    "Among safe issuers, OADR lowers nominal r−g by 26.5 per unit (p < 0.0001). Among non-safe "
    "issuers, the effect is null (p = 0.447). The pooled insignificance averages a strong negative "
    "effect (25 safe-issuer countries) and a null (111 non-safe countries), weighted toward the null."
))

add_heading(doc, '6.4 Pre/Post GFC Break', level=2)

add_table_from_data(doc,
    headers=['Period', 'Dep. Var', 'Z₁ Coeff.', 'p-value', 'N', 'Countries'],
    rows=[
        ['Pre-GFC (1990–2007)', 'Real r−g', '+68.8', '0.006', '1,561', '127'],
        ['Pre-GFC', 'Nominal r−g', '−24.2', '0.407', '1,584', '128'],
        ['Post-GFC (2010–2024)', 'Real r−g', '+4.8', '0.823', '1,204', '125'],
        ['Post-GFC', 'Nominal r−g', '+6.0', '0.793', '1,211', '125'],
    ],
    title='Table 6d: Pre/Post GFC Split',
    note='Pre-GFC: 1990–2007. Post-GFC: 2010–2024. PanelGLS with AR(1).'
)

add_para(doc, (
    "Pre-GFC, demographics significantly raised real r−g (Z₁ = +68.8, p = 0.006). Post-GFC, "
    "the signal is extinguished (p = 0.823), consistent with QE compressing real rates across "
    "aging economies. As monetary policy normalizes, the pre-GFC pattern may re-emerge."
))

add_heading(doc, '6.5 Component Decomposition', level=2)

add_table_from_data(doc,
    headers=['Dependent Variable', 'Z₁ Coeff.', 'p-value', 'N', 'Countries', 'R²'],
    rows=[
        ['Nominal bond yield', '+53.1', '0.011', '635', '23', '0.225'],
        ['Real bond yield', '+33.8', '0.085', '635', '23', '0.171'],
        ['Real GDP growth', '+35.6', '0.006', '635', '23', '0.193'],
        ['Inflation', '+26.4', '0.002', '635', '23', '0.206'],
        ['r−g (nominal)', '+21.8', '0.434', '635', '23', '0.175'],
        ['r−g (real)', '−7.7', '0.763', '635', '23', '0.135'],
    ],
    title='Table 6e: Component Decomposition (23-Country Bond Yield Sample)',
    note='Homogeneous sample with 10-year government bond yields.'
)

add_para(doc, (
    "Demographics significantly predict every component — yields, growth, inflation — yet r−g "
    "remains null. The real yield effect (+33.8) nearly equals the growth effect (+35.6), "
    "producing real r−g ≈ 0. The OADR spline and safe-issuer split succeed where the pooled Z "
    "polynomial fails because they isolate the old-age savings channel asymmetric between r and g."
))

add_heading(doc, '6.6 Reconciliation', level=2)
add_para(doc, (
    "For safe issuers above 20% OADR, aging compresses r−g by ~2.65 pp per 10pp OADR, providing "
    "fiscal breathing room. For non-safe issuers and young economies, the effects cancel or aging "
    "worsens r−g. The expenditure-revenue asymmetry is universal; the interest rate channel provides "
    "additional relief only for aged safe-issuer economies."
))

# ── 7. Debt Dynamics ─────────────────────────────────────────────────────

doc.add_page_break()
add_heading(doc, '7. Debt Dynamics and Forward Projections', level=1)

add_heading(doc, '7.1 Direct Demographic Effect on Debt', level=2)
add_para(doc, (
    "The Z₁ coefficient on annual debt change is +22.6 (p = 0.010), confirming that aging countries "
    "accumulate debt faster even after controlling for the fiscal balance, interest rates, and growth."
))

add_heading(doc, '7.2 Country Debt Trajectories', level=2)

doc.add_picture(str(FIGURES_DIR / "fig4_debt_trajectories.png"), width=Inches(5.8))
last_paragraph = doc.paragraphs[-1]
last_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_para(doc, 'Figure 4: Projected debt-to-GDP trajectories using country-specific r\u2212g (median of last '
         '5 observed years) and demographic fiscal gap projections. Values capped at 500% for readability.',
         size=9, italic=True)

# Trajectory table
traj_countries = ['JPN', 'ITA', 'USA', 'FRA', 'GBR', 'ESP', 'BRA', 'CHN', 'IND',
                  'ZAF', 'DEU', 'THA', 'MEX', 'POL', 'KOR']
traj_rows = []
for iso in traj_countries:
    if iso in trajectories['iso3'].values:
        row = trajectories[trajectories['iso3'] == iso].iloc[0]
        traj_rows.append([
            iso,
            f"{row['2024']:.0f}",
            f"{row['2030']:.0f}",
            f"{row['2040']:.0f}",
            f"{row['2050']:.0f}",
            f"{row['2060']:.0f}",
        ])

add_table_from_data(doc,
    headers=['Country', '2024', '2030', '2040', '2050', '2060'],
    rows=traj_rows,
    title='Table 7: Projected Debt-to-GDP Ratios (%)',
    note='Country-specific r−g from median of last 5 observed years. Demographic fiscal gap from estimated OADR coefficient.'
)

add_heading(doc, '7.3 Threshold Crossing Years', level=2)

doc.add_picture(str(FIGURES_DIR / "fig5_threshold_heatmap.png"), width=Inches(5.5))
last_paragraph = doc.paragraphs[-1]
last_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_para(doc, 'Figure 5: Year of threshold crossing for 16 major economies. Red = imminent; '
         'green = beyond 2060.', size=9, italic=True)

# Threshold table
thresh_rows = []
for _, r in threshold.iterrows():
    thresh_rows.append([
        r['Country'],
        r['Debt 2024'],
        r['r-g'],
        str(r['100%']),
        str(r['150%']),
        str(r['200%']),
        str(r['300%']),
    ])

add_table_from_data(doc,
    headers=['Country', 'Debt 2024', 'r−g', '100%', '150%', '200%', '300%'],
    rows=thresh_rows,
    title='Table 8: Year of Debt Threshold Crossing',
    note='Thresholds computed using country-specific r−g and demographic fiscal gap.'
)

# ── 8. Robustness ────────────────────────────────────────────────────────

doc.add_page_break()
add_heading(doc, '8. Robustness', level=1)

add_para(doc, (
    "We re-estimate key specifications across multiple subsamples and alternative variable definitions."
))

# Z1 comparison table
z1_rows = []
for _, r in z1_comp.iterrows():
    z1_rows.append([
        r['model'],
        fmt(r['coefficient'], 1),
        fmt_p(r['p_value']),
        str(int(r['n_obs'])),
        fmt(r['r_squared'], 3),
        r.get('significant', '')
    ])

add_table_from_data(doc,
    headers=['Specification', 'Z₁ Coeff.', 'p-value', 'N', 'R²', 'Sig.'],
    rows=z1_rows,
    title='Table 9: Z₁ Coefficient Across Specifications',
    note='Z₁ coefficient from Bohn and r−g models under various subsamples and variable choices.'
)

add_para(doc, (
    "The pooled Z polynomial null on r−g is confirmed across all subsamples (all p > 0.19). "
    "However, the heterogeneous results are robust: the OADR spline at 20% is significant at "
    "alternative knot locations (15%: p = 0.012; 25%: p = 0.031). The safe-issuer split "
    "(OADR = −26.5, p < 0.0001) survives leave-one-out jackknife (range: −23 to −29, all p < 0.001). "
    "The pre/post GFC break is robust to alternative cutoff years. "
    "The Bohn Z₁ coefficients are more variable but generally negative in restricted samples, "
    "suggesting that among advanced economies, aging is associated with worse primary balances."
))

# ── 9. Conclusion ────────────────────────────────────────────────────────

doc.add_page_break()
add_heading(doc, '9. Conclusion', level=1)

conclusions = [
    ("The r−g channel is heterogeneous, not null.",
     "The pooled insignificance masks an OADR spline threshold at 20%: below, aging raises r−g; "
     "above, it compresses r−g. The effect concentrates among safe issuers (OADR on r−g = −26.5, "
     "p < 0.0001 for AA− or above). The real r−g channel was significant pre-GFC but killed by QE."),
    ("The spending channel is the universal mechanism.",
     "Aging raises government expenditure 3.3 times faster than revenue. This gap is 80% "
     "non-health spending—primarily pensions and social transfers. This operates in every country."),
    ("Fiscal discipline is weaker than it appears.",
     "The Bohn coefficient is positive in the primary balance but negative in the structural "
     "balance. Even this minimal discipline has collapsed since 2020."),
    ("The window for reform is closing.",
     "Most major economies face rising debt trajectories. Countries below 20% OADR or without "
     "safe-issuer status face the full force of the expenditure gap without interest rate relief."),
]

for title, detail in conclusions:
    p = doc.add_paragraph()
    run = p.add_run(title + ' ')
    run.bold = True
    run.font.size = Pt(11)
    run = p.add_run(detail)
    run.font.size = Pt(11)
    p.paragraph_format.space_after = Pt(8)

add_para(doc, (
    "These results have direct policy implications. First, pension reform—not healthcare reform—is "
    "the primary fiscal lever, and this is universal across all countries. Second, the interest rate "
    "channel is selective: aged safe issuers benefit from r−g compression, while young and non-safe "
    "economies face the full expenditure-revenue asymmetry without rate relief. Third, the erosion "
    "of the Bohn coefficient since 2020 suggests a structural break in fiscal discipline."
))

add_para(doc, (
    "Japan illustrates the heterogeneous r−g channel at work. At 48% OADR and safe-issuer status, "
    "both channels are maximally favorable: the OADR spline compresses r−g by an estimated −22.6 pp "
    "per unit at Japan's OADR. Countries approaching 20% OADR with safe-issuer status may experience "
    "a similar transition from r−g headwinds to tailwinds in coming decades."
))

# ── Appendix: Reviewer Response Tables ────────────────────────────────────

doc.add_page_break()
add_heading(doc, 'Appendix: Supplementary Tables', level=1)

# Table A1: Same-sample PB vs SB
add_table_from_data(doc,
    headers=['Variable', 'PB Coeff (SE)', 'PB p', 'SB Coeff (SE)', 'SB p'],
    rows=[
        ['debt_lag', '0.0100 (0.0031)', '0.001', '−0.0056 (0.0030)', '0.061'],
        ['output_gap_hp', '0.1215 (0.0160)', '<0.001', '−0.0655 (0.0146)', '<0.001'],
        ['govt_exp_gap', '−0.7386 (0.0188)', '<0.001', '−0.4925 (0.0172)', '<0.001'],
        ['N', '2,216', '', '2,216', ''],
        ['Countries', '82', '', '82', ''],
        ['R²', '0.213', '', '0.122', ''],
    ],
    title='Table A1: Same-Sample Primary vs Structural Balance Bohn Test',
    note='Both regressions estimated on identical 82-country sample with both measures available.'
)

# Table A2: Structural robustness
add_table_from_data(doc,
    headers=['Specification', 'Gap Measure', 'Bohn β', 'SE', 'p-value', 'N'],
    rows=[
        ['Primary Balance', 'HP filter', '0.0054', '0.0027', '0.043', '5,061'],
        ['Structural Balance', 'HP filter', '−0.0052', '0.0030', '0.084', '2,251'],
        ['Primary Balance', 'IMF WEO', '0.0078', '0.0066', '0.239', '846'],
        ['Structural Balance', 'IMF WEO', '−0.0091', '0.0055', '0.100', '834'],
        ['Primary Balance', 'Hamilton', '0.0061', '0.0029', '0.035', '4,600'],
        ['Structural Balance', 'Hamilton', '−0.0034', '0.0034', '0.324', '2,057'],
        ['Primary Balance', 'Growth+Infl.', '0.0065', '0.0026', '0.011', '5,102'],
        ['Structural Balance', 'Growth+Infl.', '−0.0032', '0.0033', '0.343', '2,267'],
    ],
    title='Table A2: Structural Balance Robustness — Alternative Output Gap Measures',
    note='Structural Bohn coefficient negative across all four gap measures.'
)

# Table A3: Homogeneous yield r-g
add_table_from_data(doc,
    headers=['Dependent Variable', 'Z₁ Coeff.', 'SE', 'p-value', 'N', 'Countries'],
    rows=[
        ['10y Bond Yield', '53.47', '20.66', '0.010', '635', '23'],
        ['Real GDP Growth', '34.81', '13.43', '0.010', '635', '23'],
        ['r−g (bond yield)', '19.82', '29.36', '0.500', '635', '23'],
    ],
    title='Table A3: Homogeneous Yield r−g Analysis (23-Country Bond Yield Sample)',
    note='Formal equality test: β_rate − β_growth = 18.7 (SE ≈ 24.6), t = 0.76, p = 0.45.'
)

# Table A4: OADR marginal effects
add_table_from_data(doc,
    headers=['OADR', 'Expenditure ME', 'Revenue ME', 'Fiscal Gap ME'],
    rows=[
        ['5%', '105.5', '47.4', '39.3'],
        ['10%', '91.4', '47.2', '28.2'],
        ['15%', '77.3', '47.0', '17.0'],
        ['20%', '63.1', '46.8', '5.8'],
        ['25%', '49.0', '46.6', '−5.4'],
        ['30%', '34.9', '46.4', '−16.5'],
    ],
    title='Table A4: OADR Marginal Effects at Representative Levels',
    note='ME(OADR) = β_linear + 2 × β_quadratic × OADR. OADR measured as fraction (0.10 = 10%).'
)

# ── References ────────────────────────────────────────────────────────────

doc.add_page_break()
add_heading(doc, 'References', level=1)

def add_ref(doc, parts):
    """Add a reference with italic journal/book titles.

    parts is a list of (text, italic) tuples.
    """
    p = doc.add_paragraph()
    for text, is_italic in parts:
        run = p.add_run(text)
        run.font.size = Pt(10)
        run.font.name = 'Calibri'
        run.italic = is_italic
    p.paragraph_format.space_after = Pt(3)
    p.paragraph_format.left_indent = Cm(1)
    p.paragraph_format.first_line_indent = Cm(-1)

refs = [
    [("Blanchard, O. (2019). Public Debt and Low Interest Rates. ", False),
     ("American Economic Review", True), (", 109(4), 1197\u20131229.", False)],
    [("Bohn, H. (1998). The Behavior of U.S. Public Debt and Deficits. ", False),
     ("Quarterly Journal of Economics", True), (", 113(3), 949\u2013963.", False)],
    [("Bohn, H. (2007). Are Stationarity and Cointegration Restrictions Really Necessary for the Intertemporal Budget Constraint? ", False),
     ("Journal of Monetary Economics", True), (", 54(7), 1837\u20131847.", False)],
    [("Caballero, R.J., Farhi, E., & Gourinchas, P.-O. (2008). An Equilibrium Model of \u2018Global Imbalances\u2019 and Low Interest Rates. ", False),
     ("American Economic Review", True), (", 98(1), 358\u2013393.", False)],
    [("Caballero, R.J., Farhi, E., & Gourinchas, P.-O. (2017). The Safe Assets Shortage Conundrum. ", False),
     ("Journal of Economic Perspectives", True), (", 31(3), 29\u201346.", False)],
    [("Carvalho, C., Ferrero, A., & Nechio, F. (2016). Demographics and Real Interest Rates. ", False),
     ("European Economic Review", True), (", 88, 208\u2013226.", False)],
    [("Cochrane, J.H. (2011). Understanding Policy in the Great Recession. ", False),
     ("European Economic Review", True), (", 55(1), 2\u201330.", False)],
    [("Davig, T. & Leeper, E.M. (2011). Monetary-Fiscal Policy Interactions and Fiscal Stimulus. ", False),
     ("European Economic Review", True), (", 55(2), 211\u2013227.", False)],
    [("Fair, R.C. & Dominguez, K.M. (1991). Effects of the Changing U.S. Age Distribution on Macroeconomic Equations. ", False),
     ("American Economic Review", True), (", 81(5), 1276\u20131294.", False)],
    [("Ghosh, A.R., Kim, J.I., Mendoza, E.G., Ostry, J.D., & Qureshi, M.S. (2013). Fiscal Fatigue, Fiscal Space and Debt Sustainability in Advanced Economies. ", False),
     ("Economic Journal", True), (", 123(566), F4\u2013F30.", False)],
    [("Hodrick, R.J. & Prescott, E.C. (1997). Postwar U.S. Business Cycles. ", False),
     ("Journal of Money, Credit and Banking", True), (", 29(1), 1\u201316.", False)],
    [("Katagiri, M., Konishi, H., & Ueda, K. (2020). Aging and Fiscal Sustainability in a Small Open Economy. ", False),
     ("Journal of the Japanese and International Economies", True), (", 56, 101074.", False)],
    [("Lee, R. & Mason, A. (2003). The Price of Maturity. ", False),
     ("Finance and Development", True), (", 40(2).", False)],
    [("Leeper, E.M. (1991). Equilibria Under \u2018Active\u2019 and \u2018Passive\u2019 Monetary and Fiscal Policies. ", False),
     ("Journal of Monetary Economics", True), (", 27(1), 129\u2013147.", False)],
    [("Mauro, P., Romeu, R., Binder, A., & Zaman, A. (2015). A Modern History of Fiscal Prudence and Profligacy. ", False),
     ("Journal of Monetary Economics", True), (", 76, 55\u201370.", False)],
    [("Piketty, T. (2014). ", False),
     ("Capital in the Twenty-First Century", True), (". Harvard University Press.", False)],
    [("Plantin, G. (2024). Fiscal Dominance and the Return of Zero-Interest Bank Reserve Requirements. ", False),
     ("Journal of Finance", True), (", 79(4), 2631\u20132675.", False)],
    [("Rachel, L. & Smith, T.D. (2017). Are Low Real Interest Rates Here to Stay? ", False),
     ("International Journal of Central Banking", True), (", 13(3), 1\u201342.", False)],
    [("Reinhart, C.M. & Rogoff, K.S. (2010). Growth in a Time of Debt. ", False),
     ("American Economic Review", True), (", 100(2), 573\u2013578.", False)],
    [("Sargent, T.J. & Wallace, N. (1981). Some Unpleasant Monetarist Arithmetic. ", False),
     ("Federal Reserve Bank of Minneapolis Quarterly Review", True), (", 5(3), 1\u201317.", False)],
    [("Summers, L.H. (2014). U.S. Economic Prospects: Secular Stagnation, Hysteresis, and the Zero Lower Bound. ", False),
     ("Business Economics", True), (", 49(2), 65\u201373.", False)],
    [("Yared, P. (2019). Rising Government Debt: Causes and Solutions for a Decades-Old Trend. ", False),
     ("Journal of Economic Perspectives", True), (", 33(2), 115\u2013140.", False)],
]
for ref_parts in refs:
    add_ref(doc, ref_parts)

# ── Companion Papers ─────────────────────────────────────────────────────

doc.add_paragraph()
add_heading(doc, 'Companion Papers in This Series', level=2)

companion_papers = [
    "Peters, B. (2026). Demographics and Capital Flows: A 140-Country Panel Study. Working Paper.",
    "Peters, B. (2026). Demographics and Asset Prices: The Murder-Suicide of the Rentier. Working Paper.",
    "Peters, B. (2026). Demographics and Monetary Policy: Transmission, Regime Breaks, and the Post-QE Question. Working Paper.",
    "Peters, B. (2026). The Demographic Erosion of Fiscal Leverage: Twin Deficits in an Aging World. Working Paper.",
    "Peters, B. (2026). Demographics and the Trilemma: How Population Aging Shapes Exchange Rate Regime Choice. Working Paper.",
    "Peters, B. (2026). Why Feldstein-Horioka Correlations Vary: Demographics and the Savings Retention Puzzle. Working Paper.",
    "Peters, B. (2026). Demographics and Japanification: Who\u2019s Next? Working Paper.",
    "Peters, B. (2026). Demographics and Financial Crises: Age Structure as an Early Warning Signal. Working Paper.",
    "Peters, B. (2026). The Safe Asset Cliff: Demographics, Downgrades, and Collateral Scarcity. Working Paper.",
]
for cp in companion_papers:
    p = doc.add_paragraph()
    run = p.add_run(cp)
    run.font.size = Pt(10)
    run.font.name = 'Calibri'
    p.paragraph_format.space_after = Pt(3)
    p.paragraph_format.left_indent = Cm(1)
    p.paragraph_format.first_line_indent = Cm(-1)

# ── Save ──────────────────────────────────────────────────────────────────

output_path = PROJECT_DIR / "paper" / "fiscal_dominance_paper_20260305_r2.docx"
doc.save(str(output_path))
print(f"\nSaved: {output_path}")
print(f"Figures saved to: {FIGURES_DIR}")
